package com.thb.pay.service;

import com.alibaba.fastjson.JSON;
import com.google.common.collect.Lists;
import com.thb.pay.api.agent.AgentChannelServiceApi;
import com.thb.pay.api.agent.vo.resp.AgentChannelVo;
import com.thb.pay.api.merchant.MchAppServiceApi;
import com.thb.pay.api.merchant.MchInfoServiceApi;
import com.thb.pay.api.merchant.MchPayChannelServiceApi;
import com.thb.pay.api.merchant.vo.resp.MchAppVo;
import com.thb.pay.api.merchant.vo.resp.MchInfoVo;
import com.thb.pay.api.merchant.vo.resp.MchPayChannelVo;
import com.thb.pay.api.order.PayOrderServiceApi;
import com.thb.pay.api.payment.*;
import com.thb.pay.api.payment.vo.resp.*;
import com.thb.pay.api.sys.SysConfigServiceApi;
import com.thb.pay.api.sys.vo.resp.SysConfigVo;
import com.thb.pay.channel.IPaymentService;
import com.thb.pay.comm.dto.PayChannelWeightDTO;
import com.thb.pay.comm.utils.MerchantInfoContextHolder;
import com.thb.pay.comm.utils.PayChannelRandomUtils;
import com.thb.pay.core.context.ApplicationContextHelper;
import com.thb.pay.core.enums.CommConfigEnum;
import com.thb.pay.core.exception.RRException;
import com.thb.pay.core.utils.DateUtil;
import com.thb.pay.core.utils.IpInfoUtil;
import com.thb.pay.exception.PaymentGatewayEnum;
import com.xiaoleilu.hutool.collection.CollUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;

/**
 * @类名称 BaseVaildService
 * @类描述 <pre>校验服务业务</pre>
 * @作者 lucky
 * @创建时间 2019/10/24
 * @版本 v1.01
 */
@Slf4j
@Component
public class BaseValidService {

    @Autowired
    protected MchInfoServiceApi mchInfoServiceApi;

    @Autowired
    protected MchAppServiceApi mchAppServiceApi;
    @Autowired
    protected MchPayChannelServiceApi mchPayChannelServiceApi;
    @Autowired
    protected PayChannelTypeServiceApi payChannelTypeServiceApi;
    @Autowired
    protected PayChannelServiceApi payChannelServiceApi;
    @Autowired
    protected PayChannelRuleServiceApi payChannelRuleServiceApi;
    @Autowired
    protected PayChannelAccountServiceApi payChannelAccountServiceApi;
    @Autowired
    protected PayProductServiceApi payProductServiceApi;
    @Autowired
    protected CurrencyTypeServiceApi currencyTypeServiceApi;
    @Autowired
    protected SysConfigServiceApi sysConfigServiceApi;
    @Autowired
    protected Map<String, IPaymentService> paymentServiceMap;
    @Autowired
    protected AgentChannelServiceApi agentChannelServiceApi;
    @Autowired
    protected PayOrderServiceApi payOrderServiceApi;

    /**
     * @param payChannelWeightList: 权重集合信息
     * @方法名称 recursiveChannelAccount
     * @功能描述 递归获取通道对应的子账户
     * @作者: lucky
     * @创建时间: 17:49 2019/10/24
     * @return: com.thb.pay.comm.dto.PayChannelWeightDTO
     **/
    public PayChannelAccountVo recursiveChannelAccount(List<PayChannelWeightDTO> payChannelWeightList, CurrencyTypeVo currencyType, Long money) {
        // 根据权重,随机取得支付通道
        PayChannelWeightDTO payChannelWeight = PayChannelRandomUtils.getRandomByWeight(payChannelWeightList);
        BaseValidService baseValidService = ApplicationContextHelper.getBean(BaseValidService.class);
        PayChannelAccountVo payChannelAccount = baseValidService.getPayChannelAccountByChannelId(payChannelWeight.getChannelId(), currencyType, money);
        if (payChannelAccount != null) {
            return payChannelAccount;
        }
        // 如果没有取到有效子账户,则从集合中删除,然后递归取
        final CopyOnWriteArrayList<PayChannelWeightDTO> cowList = new CopyOnWriteArrayList<>(payChannelWeightList);
        for (PayChannelWeightDTO cowChannelWeight : cowList) {
            if (payChannelWeight.getChannelId().compareTo(cowChannelWeight.getChannelId()) == 0) {
                cowList.remove(cowChannelWeight);
            }
        }
        return recursiveChannelAccount(cowList, currencyType, money);
    }

    /**
     * @param payChannelRule:        支付通道风控规则
     * @param payChannelAccount: 支付通道账户信息
     * @param currencyType:      货币类型
     * @param money:             金额 单位分
     * @方法名称
     * @功能描述 校验通道账户信息风控【日期、单笔金额上下限、日交易量】
     * @作者: lucky
     * @创建时间: 14:05 2019/10/25
     * @return: com.thb.pay.api.payment.vo.resp.PayChannelAccountVo
     * @版本 v1.01
     **/
    public PayChannelAccountVo checkRiskChannelAccount(
            PayChannelRuleVo payChannelRule,
            PayChannelAccountVo payChannelAccount,
            CurrencyTypeVo currencyType,
            Long money) {

        //检查 通道账户风控状态
        if (payChannelAccount.getRiskStatus() == 0) {
            //风控关闭
            return payChannelAccount;
        }
        if (payChannelAccount.getRiskMode() != null && payChannelAccount.getRiskMode() == 1) {
            //为系统通道方案
            payChannelAccount.setRiskStatus(payChannelRule.getRiskStatus());
            payChannelAccount.setMaxDayAmount(payChannelRule.getMaxDayAmount());
            payChannelAccount.setMaxEveryAmount(payChannelRule.getMaxEveryAmount());
            payChannelAccount.setMinEveryAmount(payChannelRule.getMinEveryAmount());
            payChannelAccount.setTradeStartTime(payChannelRule.getTradeStartTime());
            payChannelAccount.setTradeEndTime(payChannelRule.getTradeEndTime());
        }
        //TODO 金钱全部转化成 RMB 的金额进行比较校验风控 后期增加
        if (money < payChannelAccount.getMinEveryAmount() || money > payChannelAccount.getMaxEveryAmount()) {
            log.warn("[风控系统]单笔交易金额触发风控.payPassageAccountId={},maxEveryAmount={},minEveryAmount={},money={}", payChannelAccount.getId(), payChannelAccount.getMaxEveryAmount(), payChannelAccount.getMinEveryAmount(), money);
            //TODO 后期 加入前置拦截,当前商户被锁定
            return null;
        }
        if (DateUtil.getDateStart(new Date()).getTime() < DateUtil.StringToDate(payChannelAccount.getTradeStartTime()).getTime()
                || DateUtil.getDateEnd(new Date()).getTime() > DateUtil.StringToDate(payChannelAccount.getTradeEndTime()).getTime()) {
            log.warn("[风控系统]当前交易时间触发风控.payPassageAccountId={},maxEveryAmount={},minEveryAmount={},money={}", "[风控系统]", payChannelAccount.getId(), payChannelAccount.getMaxEveryAmount(), payChannelAccount.getMinEveryAmount(), money);
            //TODO 后期 加入前置拦截,当前商户被锁定
            return null;
        }
        //TODO 校验该通道账户的日交易额订单是否超过风控上限
        return payChannelAccount;
    }

    /**
     * @param mchPayChannel:    商户对应支付通道信息
     * @param currencyType:     货币类型
     * @param money:            金额[单位分]
     * @param channelAccountId: 前端传入指定支付通道账户[可为空]
     * @方法名称 getPayChannelAccount
     * @功能描述 获取通道账户开户明细
     * @作者: lucky
     * @创建时间: 16:46 2019/10/24
     * @return: com.thb.pay.api.payment.vo.resp.PayChannelAccountVo
     **/
    public PayChannelAccountVo getPayChannelAccount(MchPayChannelVo mchPayChannel,
                                                    CurrencyTypeVo currencyType,
                                                    Long money,
                                                    Long channelAccountId) {
        PayChannelAccountVo payChannelAccount;
        if (channelAccountId != null && channelAccountId > 0) {
            payChannelAccount = this.getPayChannelAccountByChannelAccountId(mchPayChannel.getMchId(), channelAccountId);
            PayChannelRuleVo payChannelRule = this.getPayChannelRuleByChannelId(payChannelAccount.getChannelId());
            //检验是否触发风控
            return this.checkRiskChannelAccount(payChannelRule, payChannelAccount, currencyType, money);
        }
        //根据商户通道设置的模式 进行选择对应的通道账户
        payChannelAccount = this.getPayChannelAccountByMode(mchPayChannel, currencyType, money);
        if (payChannelAccount == null) {
            throw RRException.build(PaymentGatewayEnum.PAY_PASSAGE_ACCOUNT_NOT_EXIST_ERROR, String.valueOf(mchPayChannel.getMchId()), String.valueOf(mchPayChannel.getProductId()));
        }
        return payChannelAccount;
    }

    public PayChannelAccountVo getPayChannelAccountByMode(MchPayChannelVo mchPayChannel,
                                                          CurrencyTypeVo currencyType,
                                                          Long money) {
        Long channelAccountId = mchPayChannel.getChannelAccountId();
        PayChannelAccountVo payChannelAccount;
        if (mchPayChannel.getChannelMode() != null && mchPayChannel.getChannelMode() == 1) {//1-单独,2-轮询
            if (channelAccountId != null && channelAccountId > 0) {//商户对应配置的支付通道有指定具体通道三方账号
                payChannelAccount = this.getPayChannelAccountByChannelAccountId(mchPayChannel.getMchId(), channelAccountId);
                PayChannelRuleVo payChannelRule = this.getPayChannelRuleByChannelId(mchPayChannel.getChannelId());
                //风控
                return this.checkRiskChannelAccount(payChannelRule, payChannelAccount, currencyType, money);
            } else {
                //商户对应配置的支付通道，在这未配置具体的三方支付账号 , 按权重获取支付通道对应的通道账号信息集合随机一个
                return this.getPayChannelAccountByChannelId(mchPayChannel.getChannelId(), currencyType, money);
            }
        } else if (mchPayChannel.getChannelMode() != null && mchPayChannel.getChannelMode() == 2) {
            // 轮询通道参数
            String pollParamJson = mchPayChannel.getPollParam();
            List<PayChannelWeightDTO> payPassageWeightList = JSON.parseArray(pollParamJson, PayChannelWeightDTO.class);
            if (CollUtil.isEmpty(payPassageWeightList)) {
                throw RRException.build(PaymentGatewayEnum.MERCHANT_PAY_PASSAGE_NOT_POLL_ERROR, String.valueOf(mchPayChannel.getMchId()), String.valueOf(mchPayChannel.getProductId()));
            }
            return this.recursiveChannelAccount(payPassageWeightList, currencyType, money);
        } else {
            return this.getPayChannelAccountByChannelId(mchPayChannel.getChannelId(), currencyType, money);
        }

    }

    public PayChannelRuleVo getPayChannelRuleByChannelId(Long channelId) {
        PayChannelRuleVo payChannelRule = payChannelRuleServiceApi.getByChannelId(channelId);
        if (payChannelRule == null) {
            throw RRException.build(PaymentGatewayEnum.PAY_PASSAGE_DISABLE_ERROR, String.valueOf(channelId));
        }
        if (payChannelRule.getStatus() == 0 || payChannelRule.getDelFlag() == 1) {
            throw RRException.build(PaymentGatewayEnum.PAY_PASSAGE_DISABLE_ERROR, String.valueOf(channelId));
        }
        return payChannelRule;
    }

    public List<PayChannelAccountVo> getEnableChannelAccountByChannelId(Long channelId) {
        List<PayChannelAccountVo> payChannelAccountList = payChannelAccountServiceApi.findListByChannelId(channelId);
        List<PayChannelAccountVo> enableChannelAccountList = Lists.newArrayList();
        if (CollUtil.isNotEmpty(payChannelAccountList)) {
            payChannelAccountList.forEach(payChannelAccountVo -> {
                if (payChannelAccountVo.getStatus() != null && payChannelAccountVo.getStatus() == 1
                        && payChannelAccountVo.getDelFlag() != null && payChannelAccountVo.getDelFlag() == 0) {
                    enableChannelAccountList.add(payChannelAccountVo);
                }
            });
        }
        if (CollUtil.isEmpty(enableChannelAccountList)) {
            throw RRException.build(PaymentGatewayEnum.PAY_PASSAGE_NOT_ACCOUNT_ERROR, String.valueOf(channelId));
        }
        return enableChannelAccountList;
    }

    public PayChannelAccountVo getPayChannelAccountByChannelId(Long channelId,
                                                               CurrencyTypeVo currencyType,
                                                               Long money) {
        PayChannelAccountVo payChannelAccount = null;
        PayChannelRuleVo payChannelRule = this.getPayChannelRuleByChannelId(channelId);
        List<PayChannelAccountVo> enableChannelAccountList = this.getEnableChannelAccountByChannelId(channelId);
        //随机顺序
        Collections.shuffle(enableChannelAccountList);
        for (PayChannelAccountVo enablePayChannelAccount : enableChannelAccountList) {
            payChannelAccount = this.checkRiskChannelAccount(payChannelRule, enablePayChannelAccount, currencyType, money);
            if (payChannelAccount != null) {
                break;
            }
        }
        return payChannelAccount;
    }

    public PayChannelAccountVo getPayChannelAccountByChannelAccountId(Long merchantId, Long channelAccountId) {
        PayChannelAccountVo payChannelAccount;
        payChannelAccount = payChannelAccountServiceApi.getById(channelAccountId);
        if (payChannelAccount == null) {
            throw RRException.build(PaymentGatewayEnum.PAY_PASSAGE_ACCOUNT_DISABLE_ERROR, String.valueOf(channelAccountId), String.valueOf(merchantId));
        }
        if (payChannelAccount.getStatus() == 0 || payChannelAccount.getDelFlag() == 1) {
            throw RRException.build(PaymentGatewayEnum.PAY_PASSAGE_ACCOUNT_DISABLE_ERROR, String.valueOf(channelAccountId), String.valueOf(merchantId));
        }
        return payChannelAccount;
    }

    public MchPayChannelVo validMerchantPayChannel(Long merchantId, Long productId) {
        MchPayChannelVo mchPayPassage = mchPayChannelServiceApi.getByMerchantIdAndProductId(merchantId, productId);
        if (mchPayPassage == null) {
            throw RRException.build(PaymentGatewayEnum.MERCHANT_PAY_PASSAGE_NOT_EXIST_ERROR, String.valueOf(merchantId), String.valueOf(productId));
        }
        if (mchPayPassage.getDelFlag() == 1) {
            throw RRException.build(PaymentGatewayEnum.MERCHANT_PAY_PASSAGE_DISABLE_ERROR, String.valueOf(merchantId), String.valueOf(productId));
        }
        if (mchPayPassage.getStatus() == 0) {
            throw RRException.build(PaymentGatewayEnum.MERCHANT_PAY_PASSAGE_DISABLE_ERROR, String.valueOf(merchantId), String.valueOf(productId));
        }
        return mchPayPassage;
    }

    public Long validMoney(String moneyStr) {
        if (!NumberUtils.isDigits(moneyStr)) {
            throw RRException.build(PaymentGatewayEnum.MONEY_NOT_SUPPORT_ERROR);
        }
        Long money = Long.parseLong(moneyStr);
        if (money <= 0) {
            throw RRException.build(PaymentGatewayEnum.MONEY_NOT_SUPPORT_ERROR);
        }
        return money;
    }

    public PayProductVo validProduct(Long productId) {
        PayProductVo payProduct = payProductServiceApi.getById(productId);
        if (payProduct == null) {
            throw RRException.build(PaymentGatewayEnum.PRODUCT_NOT_EXIST_ERROR);
        }
        if (payProduct.getDelFlag() == 1) {
            throw RRException.build(PaymentGatewayEnum.PRODUCT_NOT_EXIST_ERROR, String.valueOf(productId));
        }
        if (payProduct.getStatus() == 0) {
            throw RRException.build(PaymentGatewayEnum.PRODUCT_DISABLE_ERROR, String.valueOf(productId));
        }
        return payProduct;
    }

    public CurrencyTypeVo validCurrency(String currencyCode) {
        CurrencyTypeVo currencyType = currencyTypeServiceApi.getByCode(currencyCode);
        if (currencyType == null) {
            throw RRException.build(PaymentGatewayEnum.CURRENCY_NOT_SUPPORT_ERROR);
        }
        if (currencyType.getDelFlag() == 1) {
            throw RRException.build(PaymentGatewayEnum.CURRENCY_NOT_SUPPORT_ERROR, currencyCode);
        }
        if (currencyType.getStatus() == 0) {
            throw RRException.build(PaymentGatewayEnum.CURRENCY_DISABLE_ERROR, currencyCode);
        }
        return currencyType;
    }

    public void validMerchantApp(Long appId) {
        MchAppVo mchApp = mchAppServiceApi.getById(appId);
        if (mchApp == null) {
            throw RRException.build(PaymentGatewayEnum.APPID_NOT_EXIST_ERROR);
        }
        if (mchApp.getDelFlag() == 1) {
            throw RRException.build(PaymentGatewayEnum.APPID_NOT_EXIST_ERROR, String.valueOf(appId));
        }
        if (mchApp.getStatus() == 0) {
            throw RRException.build(PaymentGatewayEnum.APPID_DISABLE_ERROR, String.valueOf(appId));
        }
    }

    public MchInfoVo validMerchant(Long merchantId) {
        //1.商户校验
        MchInfoVo merchantInfo = getMerchantInfo(merchantId);
        //2.检查系统是否开启商户支付黑白名单校验
        this.checkMerchantWhiteIp(merchantInfo);

        return merchantInfo;
    }

    public MchInfoVo getMerchantInfo(Long merchantId) {
        MchInfoVo merchantInfo = mchInfoServiceApi.getById(merchantId);
        if (merchantInfo == null) {
            throw RRException.build(PaymentGatewayEnum.SIGN_ERROR);
        }
        //存入上下文
        MerchantInfoContextHolder.set(merchantInfo);
        if (merchantInfo.getDelFlag() == 1) {
            throw RRException.build(PaymentGatewayEnum.MERCHANT_NOT_EXIST_ERROR, String.valueOf(merchantId));
        }
        if (merchantInfo.getStatus() == 0) {
            throw RRException.build(PaymentGatewayEnum.MERCHANT_DISABLE_ERROR, String.valueOf(merchantId));
        }
        return merchantInfo;
    }

    public void checkMerchantWhiteIp(MchInfoVo mchInfo) {
        //校验系统是否开启商户 白名单校验
        SysConfigVo whiteIpConfig = sysConfigServiceApi.getByCode(CommConfigEnum.MCHANT_PAY_WHITE_IP.code());
        if (whiteIpConfig == null) {
            return;
        }
        String isOpenWhiteAllow = whiteIpConfig.getValue();
        if (StringUtils.isNotBlank(isOpenWhiteAllow) && "true".equals(isOpenWhiteAllow)) {
            HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
            String requestIp = IpInfoUtil.getIp(request);
            List<String> whiteIpList = Lists.newArrayList();
            List<String> blackIpList = Lists.newArrayList();
            if (StringUtils.isNotBlank(mchInfo.getPayWhiteIp())) {
                whiteIpList = Arrays.asList(mchInfo.getPayBlackIp().split(","));
            }
            if (StringUtils.isNotBlank(mchInfo.getPayBlackIp())) {
                blackIpList = Arrays.asList(mchInfo.getPayBlackIp().split(","));
            }
            boolean isAllow = IpInfoUtil.ipAllow(requestIp, whiteIpList, blackIpList);
            if (!isAllow) {
                throw RRException.build(PaymentGatewayEnum.IP_ACCESS_DENIED_ERROR, requestIp);
            }
        }
    }

    public String getChannelById(Long channelId) {
        PayChannelVo payChannel = payChannelServiceApi.getById(channelId);
        return payChannel.getCode();
    }

    public AgentChannelVo getAgentChannelByAgentId(Long agentId, Long productId) {
        AgentChannelVo agentChannelVo = agentChannelServiceApi.getByAgentIdAndProductId(agentId, productId);
        if (agentChannelVo != null && agentChannelVo.getStatus() == 1) {
            return agentChannelVo;
        }
        return null;
    }


    public String getPaymentChannelNameService(Long channelId) {
        PayChannelTypeVo payChannelType = payChannelTypeServiceApi.getById(channelId);
        return payChannelType.getCode().toLowerCase() + "PaymentService";
    }
}
